#include <QFileDialog>
#include <QTableView>
#include <QTextCodec>
#include "csv-manager.h"
#include "csv-manager-dialog.h"
#include "debug-helper.h"
#include "icon-store.h"
#include "settings.h"
#include "ui_csv-manager-dialog.h"
#include "widget-helper.h"

CsvManagerDialog::CsvManagerDialog (QWidget *parent)
    : QDialog(parent), ui(new Ui::CsvManagerDialog), m_showOptionsText("Opcje >>"), m_hideOptionsText("Opcje <<"),
      m_optionsVisible(false), m_fileNameIsValid(false), m_separatorIsValid(false), m_dialogMode(ExportMode),
      m_canExportSelectedCells(false), m_heightWhenOptionsHidden(0), m_pixmapSize(16), m_shownOnce(false),
      m_openWithOptionsVisible(false), m_tableView(0)
{
}

CsvManagerDialog::~CsvManagerDialog()
{
    delete ui;
}

void CsvManagerDialog::initialize()
{
    ui->setupUi(this);
    setupAppearance();
    loadSettings();
    setupConnections();

    validateFileName(ui->fileNameLineEdit->text());
    validateSeparator(ui->separatorLineEdit->text());
}

void CsvManagerDialog::setupAppearance()
{
    setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint));
    setWindowIcon(IconStore::icon(m_dialogMode == ExportMode ? IconStore::DocumentExport : IconStore::DocumentImport,
                                  m_pixmapSize));

    ui->columnsVisibilityLabel->hide();
    ui->allColumnsRadioButton->hide();
    ui->visibleColumnsRadioButton->hide();
    // create radio buttons groups
    QButtonGroup* cellsRangeGroup = new QButtonGroup(this);
    cellsRangeGroup->addButton(ui->allCellsRadioButton);
    cellsRangeGroup->addButton(ui->selectedCellsRadioButton);
    QButtonGroup* columnsGroup = new QButtonGroup(this);
    columnsGroup->addButton(ui->allColumnsRadioButton);
    columnsGroup->addButton(ui->visibleColumnsRadioButton);

    // setting mode specific things
    switch (m_dialogMode)
    {
    case ImportMode:
        setWindowTitle("Import pliku CSV");
        ui->cellsHorizontalSpacer->changeSize(0, 0, QSizePolicy::Ignored, QSizePolicy::Ignored);
        ui->headerCheckBox->setText("Załaduj pierwszy wiersz do nagłówka.");
        break;

    case ExportMode:
        setWindowTitle("Eksport pliku CSV");
        ui->headerCheckBox->setText("Dodaj nagłówek jako pierwszy wiersz.");
        enableExportSelectedCells(m_tableView->selectionModel()->hasSelection());
        if (m_canExportSelectedCells == false)
            ui->selectedCellsRadioButton->setDisabled(true);
        break;

    default:
        Q_ASSERT(m_dialogMode == ImportMode || m_dialogMode == ExportMode);
    }

    setupCodecs();
    setupWidgetsSizes();
}

void CsvManagerDialog::setupCodecs()
{
    QList<QByteArray> availableCodecs = QTextCodec::availableCodecs();
    qSort(availableCodecs);
    foreach (const QByteArray& codec, availableCodecs)
        ui->codecsComboBox->addItem(QString(codec));
}

void CsvManagerDialog::setupWidgetsSizes()
{
    QList<QWidget*> widgets;
    if (m_dialogMode == ExportMode)
    {
        widgets << ui->fileNameLabel << ui->cellsRangeLabel << ui->columnsVisibilityLabel << ui->headerLabel
                << ui->codecsLabel << ui->separatorLabel << ui->numbersFormatLabel;
    }
    else
    {
        widgets << ui->fileNameLabel << ui->headerLabel << ui->codecsLabel << ui->separatorLabel;
    }

    WidgetHelper::equalizeWidgetWidth(widgets);
}

void CsvManagerDialog::setupConnections()
{
    connect (ui->browsePushButton, &QPushButton::clicked, this, &CsvManagerDialog::browseFiles);
    connect (ui->toggleOptionsPushButton, &QPushButton::clicked, this, &CsvManagerDialog::toggleOptions);
    connect (ui->fileNameLineEdit, &QLineEdit::textChanged, this, &CsvManagerDialog::validateFileName);
    connect (ui->separatorLineEdit, &QLineEdit::textChanged, this, &CsvManagerDialog::validateSeparator);
    connect (this, &CsvManagerDialog::accepted, this, &CsvManagerDialog::saveSettings);
}

void CsvManagerDialog::loadSettings()
{
    Q_ASSERT(m_dialogMode == ImportMode || m_dialogMode == ExportMode);

    Settings settings;
    m_documentsPath = settings.value("documentsPath").toString();
    settings.beginGroup(metaObject()->className());

    // setup icons
    m_invalidStatusPixmap = IconStore::pixmap(IconStore::StatusRejected, m_pixmapSize);
    m_validStatusPixmap = IconStore::pixmap(IconStore::StatusOk, m_pixmapSize);

    if (m_dialogMode == ImportMode)
    {
        ui->fileNameLineEdit->setText(QDir::toNativeSeparators(settings.value("fileName").toString()));
    }
    else
    {
        if (!m_documentsPath.isEmpty())
            ui->fileNameLineEdit->setText(QDir::toNativeSeparators(m_documentsPath)
                                          + QDir::toNativeSeparators("/"));
    }

    if (m_dialogMode == ExportMode)
    {
        if (m_canExportSelectedCells)
        {
            SmGlobal::CellsRange cellsRange = static_cast<SmGlobal::CellsRange>
                                            (settings.value("cellsRange", SmGlobal::AllCells).toInt());
            Q_ASSERT(cellsRange == SmGlobal::AllCells || cellsRange == SmGlobal::SelectedCells);

            if (cellsRange == SmGlobal::AllCells)
                ui->allCellsRadioButton->setChecked(true);
            else
                ui->selectedCellsRadioButton->setChecked(true);
        }
        else
        {
            ui->allCellsRadioButton->setChecked(true);
        }
        Q_ASSERT(ui->allCellsRadioButton->isChecked() != ui->selectedCellsRadioButton->isChecked());

        SmGlobal::ColumnsVisibility columnsVisibility = static_cast<SmGlobal::ColumnsVisibility>
                                                      (settings.value("columnsVisibility", SmGlobal::VisibleColumns).toInt());
        Q_ASSERT(columnsVisibility == SmGlobal::AllColumns || columnsVisibility == SmGlobal::VisibleColumns);

        if (columnsVisibility == SmGlobal::AllColumns)
            ui->allColumnsRadioButton->setChecked(true);
        else
            ui->visibleColumnsRadioButton->setChecked(true);
        Q_ASSERT(ui->allColumnsRadioButton->isChecked() != ui->visibleColumnsRadioButton->isChecked());

        ui->numbersFormatCheckBox->setChecked(settings.value("forceNumbersFormat", true).toBool());
    }
    ui->headerCheckBox->setChecked(settings.value("withHeader", true).toBool());
    ui->codecsComboBox->setCurrentIndex(ui->codecsComboBox->findText(settings.value("codec", "System").toString()));
    ui->separatorLineEdit->setText(settings.value("separator", ',').toChar());
    m_openWithOptionsVisible = settings.value("optionsVisible", false).toBool();
}

void CsvManagerDialog::enableExportSelectedCells (bool enable)
{
    m_canExportSelectedCells = enable;
    ui->selectedCellsRadioButton->setEnabled(enable);
}

void CsvManagerDialog::browseFiles()
{
    Q_ASSERT(m_dialogMode == ImportMode || m_dialogMode == ExportMode);

    QString fileName;
    if (m_dialogMode == ImportMode)
        fileName = QFileDialog::getOpenFileName(this, "Importuj plik CSV", m_documentsPath, "Pliki CSV (*.csv)");
    else
        fileName = QFileDialog::getSaveFileName(this, "Eksportuj plik CSV", m_documentsPath, "Pliki CSV (*.csv)");

    if (fileName.isEmpty())
        return;

    ui->fileNameLineEdit->setText(QDir::toNativeSeparators(fileName));
    m_documentsPath = QFileInfo(fileName).absolutePath();
}

void CsvManagerDialog::toggleOptions()
{
    if (m_optionsVisible)
        hideOptions();
    else
        showOptions();
}

void CsvManagerDialog::showOptions()
{
    Q_ASSERT (m_dialogMode == ImportMode || m_dialogMode == ExportMode);

    ui->optionsGroupBox->show();
    if (m_dialogMode == ExportMode)
    {
        ui->cellsRangeLabel->show();
        ui->allCellsRadioButton->show();
        ui->selectedCellsRadioButton->show();
//        ui->columnsVisibilityLabel->show();
//        ui->allColumnsRadioButton->show();
//        ui->visibleColumnsRadioButton->show();
        ui->numbersFormatLabel->show();
        ui->numbersFormatCheckBox->show();
    }
    else
    {
        ui->cellsRangeLabel->hide();
        ui->allCellsRadioButton->hide();
        ui->selectedCellsRadioButton->hide();
//        ui->columnsVisibilityLabel->hide();
//        ui->allColumnsRadioButton->hide();
//        ui->visibleColumnsRadioButton->hide();
        ui->numbersFormatLabel->hide();
        ui->numbersFormatCheckBox->hide();
    }
    ui->headerLabel->show();
    ui->headerCheckBox->show();
    ui->codecsLabel->show();
    ui->codecsComboBox->show();
    ui->separatorLabel->show();
    ui->separatorLineEdit->show();
    ui->separatorStatusImageLabel->show();
    ui->separatorStatusLabel->show();

    ui->toggleOptionsPushButton->setText(m_hideOptionsText);
    m_optionsVisible = true;
    setFixedHeight(minimumSizeHint().height());
}

void CsvManagerDialog::hideOptions()
{
    ui->optionsGroupBox->hide();
    ui->cellsRangeLabel->hide();
    ui->allCellsRadioButton->hide();
    ui->selectedCellsRadioButton->hide();
//    ui->columnsVisibilityLabel->hide();
//    ui->allColumnsRadioButton->hide();
//    ui->visibleColumnsRadioButton->hide();
    ui->headerLabel->hide();
    ui->headerCheckBox->hide();
    ui->numbersFormatLabel->hide();
    ui->numbersFormatCheckBox->hide();
    ui->codecsLabel->hide();
    ui->codecsComboBox->hide();
    ui->separatorLabel->hide();
    ui->separatorLineEdit->hide();
    ui->separatorStatusImageLabel->hide();
    ui->separatorStatusLabel->hide();

    ui->toggleOptionsPushButton->setText(m_showOptionsText);
    m_optionsVisible = false;
    setFixedHeight(m_heightWhenOptionsHidden);
}

void CsvManagerDialog::validateFileName (const QString& fileName)
{
    Q_ASSERT(m_dialogMode == ImportMode || m_dialogMode == ExportMode);

    if (fileName.isEmpty())
    {
        if (m_dialogMode == ExportMode)
            setFileNameStatus("Podaj ścieżkę, gdzie zapisać plik CSV.", IntermediateStatus);
        else
            setFileNameStatus("Podaj ścieżkę, skąd załadować plik CSV.", IntermediateStatus);
        return;
    }

    if (fileName.contains('/') && fileName.contains('\\'))
    {
        setFileNameStatus("Ścieżka nie może zawierać jednocześnie znaków \"/\" oraz \"\\\".", InvalidStatus);
        return;
    }


    QFileInfo fileInfo(QDir::fromNativeSeparators(fileName));

#ifdef Q_OS_WIN
    // check if the file path starts with a drive letter
    if (fileInfo.isRelative() || !fileName.contains(QRegExp("^[A-Za-z]:\\\\")))
#else
    if (fileInfo.isRelative())
#endif
    {
        setFileNameStatus("Wymagane jest podanie pełnej ścieżki do pliku.", InvalidStatus);
        return;
    }

    if (fileInfo.isDir() || fileInfo.absoluteFilePath().endsWith('/'))
    {
        setFileNameStatus("Podana ścieżka nie prowadzi do pliku.", InvalidStatus);
        return;
    }

    if (!fileInfo.fileName().endsWith(".csv", Qt::CaseInsensitive))
    {
        setFileNameStatus("Podany plik nie jest w formacie CSV.", InvalidStatus);
        return;
    }

#ifdef Q_OS_WIN
    // check if the file name contains illegal characters like :, *, ?, ", <, > or |
    if (fileInfo.fileName().contains(QRegExp("[:\\*\\?\"<>\\|]")))
    {
        setFileNameStatus("Podany plik zawiera niedozwolone znaki (: * ? \" < > |).", InvalidStatus);
        return;
    }
#endif

    if (m_dialogMode == ExportMode)
    {
        if (fileInfo.exists())
        {
            if (!fileInfo.isWritable())
            {
                setFileNameStatus("Podany plik nie jest zapisywalny.", InvalidStatus);
                return;
            }

            setFileNameStatus("Podany plik istnieje i zostanie nadpisany.", ValidStatus);
            return;
        }
    }
    else
    {
        if (!fileInfo.exists())
        {
            setFileNameStatus("Podany plik nie istnieje.", InvalidStatus);
            return;
        }


        if (!fileInfo.isReadable())
        {
            setFileNameStatus("Podany plik nie jest odczytywalny.", InvalidStatus);
            return;
        }
    }

    setFileNameStatus("Ścieżka do pliku jest poprawna.", ValidStatus);
}

void CsvManagerDialog::validateSeparator (const QString& separator)
{
    if (separator.length() == 1)
        setSeparatorStatus("", true);
    else
        setSeparatorStatus("Separator musi się składać z jednego znaku.", false);

    tryActivateButtons();
}

void CsvManagerDialog::setFileNameStatus (const QString& statusText, const Status status)
{
    ui->fileNameStatusLabel->setText(statusText);

    switch (status)
    {
    case InvalidStatus:
        ui->fileNameStatusImageLabel->setPixmap(m_invalidStatusPixmap);
        m_fileNameIsValid = false;
        break;

    case IntermediateStatus:
        ui->fileNameStatusImageLabel->clear();
        m_fileNameIsValid = false;
        break;

    case ValidStatus:
        ui->fileNameStatusImageLabel->setPixmap(m_validStatusPixmap);
        m_fileNameIsValid = true;
        break;

    default:
        fatalError(status);
    }

    tryActivateButtons();
}

void CsvManagerDialog::setSeparatorStatus (const QString& status, bool isValid)
{
    m_separatorIsValid = isValid;
    ui->separatorStatusLabel->setText(status);

    if (isValid)
        ui->separatorStatusImageLabel->clear();
    else
        ui->separatorStatusImageLabel->setPixmap(m_invalidStatusPixmap);
}

void CsvManagerDialog::tryActivateButtons()
{
    if (m_fileNameIsValid && m_separatorIsValid)
        ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
    else
        ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(true);
}

QString CsvManagerDialog::fileName()
{
    return QDir::fromNativeSeparators(ui->fileNameLineEdit->text());
}

SmGlobal::CellsRange CsvManagerDialog::cellsRange()
{
    Q_ASSERT(ui->allCellsRadioButton->isChecked() || ui->selectedCellsRadioButton->isChecked());

    if (ui->allCellsRadioButton->isChecked())
        return SmGlobal::AllCells;
    else
        return SmGlobal::SelectedCells;
}

SmGlobal::ColumnsVisibility CsvManagerDialog::columnsVisibility()
{
    Q_ASSERT(ui->allColumnsRadioButton->isChecked() || ui->visibleColumnsRadioButton->isChecked());

    if (ui->allColumnsRadioButton->isChecked())
        return SmGlobal::AllColumns;
    else
        return SmGlobal::VisibleColumns;
}

bool CsvManagerDialog::withHeader()
{
    return ui->headerCheckBox->isChecked();
}

QTextCodec* CsvManagerDialog::codec()
{
    return QTextCodec::codecForName(ui->codecsComboBox->currentText().toUtf8());
}

QChar CsvManagerDialog::separator()
{
    Q_ASSERT(ui->separatorLineEdit->text().length() == 1);
    return ui->separatorLineEdit->text().at(0);
}

bool CsvManagerDialog::forceNumbersFormat()
{
    return ui->numbersFormatCheckBox->isChecked();
}

void CsvManagerDialog::setDialogMode(CsvManagerDialog::DialogMode dialogMode)
{
    m_dialogMode = dialogMode;
}

void CsvManagerDialog::setTableView(QTableView *tableView)
{
    m_tableView = tableView;
}

void CsvManagerDialog::saveSettings()
{
    Settings settings;
    settings.setValue("documentsPath", m_documentsPath);
    settings.beginGroup(metaObject()->className());
    settings.setValue("fileName", QDir::fromNativeSeparators(ui->fileNameLineEdit->text()));
    if (m_dialogMode == ExportMode)
    {
        if (ui->allCellsRadioButton->isChecked())
            settings.setValue("cellsRange", SmGlobal::AllCells);
        else
            settings.setValue("cellsRange", SmGlobal::SelectedCells);

        if (ui->allColumnsRadioButton->isChecked())
            settings.setValue("columnsVisibility", SmGlobal::AllColumns);
        else
            settings.setValue("columnsVisibility", SmGlobal::VisibleColumns);

        settings.setValue("forceNumbersFormat", ui->numbersFormatCheckBox->isChecked());
    }
    settings.setValue("withHeader", ui->headerCheckBox->isChecked());
    settings.setValue("codec", ui->codecsComboBox->currentText());
    Q_ASSERT(ui->separatorLineEdit->text().length() == 1);
    settings.setValue("separator", ui->separatorLineEdit->text().at(0));
    settings.setValue("optionsVisible", m_optionsVisible);
    settings.endGroup();
}

void CsvManagerDialog::setVisible (bool visible)
{
    if (!m_shownOnce && visible)
    {
        // a hack for getting ideal size when options are hidden
        // so that the layout will behave as if it had Fixed Constraint
        // but thanks to this we can still resize the width
        // it's needed because sizeHint() before and after show() differs
        hideOptions();
        QDialog::setVisible(visible);
        m_heightWhenOptionsHidden = minimumSizeHint().height();

        if (m_openWithOptionsVisible)
            showOptions();
        else
            setFixedHeight(m_heightWhenOptionsHidden);

        m_shownOnce = true;
    }
    else
    {
        QDialog::setVisible(visible);
    }
}

void CsvManagerDialog::accept()
{
    CsvManager csvManager;
    if (m_dialogMode == ExportMode)
    {
        csvManager.exportToCsv(m_tableView, fileName(), cellsRange(), columnsVisibility(), withHeader(),
                               forceNumbersFormat(), separator(), codec());

        switch (csvManager.state())
        {
        case CsvManager::NothingToExport:
            emit notifyWarning("Eksport danych nie powiódł się.", "Nie ma danych do wyeksportowania.");
            break;
        case CsvManager::CannotExportToFile:
            emit notifyWarning("Eksport danych nie powiódł się.", QString("Nie można zapisać danych do pliku:\n%1")
                               .arg(QDir::toNativeSeparators(csvManager.filePath())));
            break;
        case CsvManager::ExportSuccessful:
            emit notifyInformation("Eksport danych powiódł się.",
                                   QString("<a href=%1>Otwórz katalog</a> lub <a href=%2>otwórz plik</a>.")
                                   .arg(csvManager.path()).arg(csvManager.filePath()));
            break;
        default:
            fatalError(csvManager.state());
            break;
        }
    }

    QDialog::accept();
}
